/*
 * Computer Graphics:  Andrew Hanson, Indiana University
 * GLUT  Graphics Exercise Template 
 *

 cc -o template.linux template.c -lglut -lGLU -lGL -lm

 *
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>

/* Local Graphics Definitions. Also in /l/b481/include, /l/b581/include */
#include "defs.h"

/*  double y0,y1;   math.h conflict */

/***************************************************************/
/******************** MY GLOBAL VARIABLES **********************/
/***************************************************************/

// these will be used for glProject and glUnProject
GLint viewport[4];
GLdouble mvmatrix[16], projmatrix[16];
GLdouble wx, wy, wz;

int fixed_point_x, fixed_point_y;
#define MID_TERM_SCALING 1
#define MID_TERM_PROPER_SCALING 2
#define BUG_CATCHER 3
#define TRANSFORM_TEAPOT 4

int transformation_mode = 0;
#define TRANSFORMATION_MODES 7
GLboolean drawLinesInBugCatcher= GL_FALSE;

int drawingMode= BUG_CATCHER;

int mouse_down_x, mouse_down_y;

int mouseButtons = 0;

int MouseModifiers = 0;

int X=0, Y=0, dX=0, dY=0;

GLboolean mouse_just_down= GL_FALSE;

int main_window;
int main_height, main_width;

float mat[16];
GLboolean refresh_display= GL_TRUE;

float rand_x, rand_y;
GLboolean bug_found= GL_TRUE;

/***************************************************************/
/********************     MY FUNCTIONS    **********************/
/***************************************************************/

int min(int a, int b) {
	return a<b ? a : b;
}
//void drawPolygon() {
//
//	int large_rect_size = min(main_height, main_width) - 250;
//
//	glColor3f(1.0, 1.0, 1.0);
//	int x_init = 50, y_init = large_rect_size+50;
//	glRecti(x_init, y_init, y_init, x_init);
//
//	glColor3f(0.0, 0.0, 0.0);
//	int i, j, rect_side = 20;
//	for (i = 0; i < large_rect_size; i += rect_side*2) {
//		x_init = (i % 2) == 0 ? 50 : 50 + rect_side;
//		y_init = (i % 2) == 0 ? large_rect_size+50 : large_rect_size + 50
//				- rect_side;
//		for (j = 0; j < large_rect_size; j += rect_side*2) {
//			glRecti(x_init + i, y_init - j, x_init + i + rect_side, y_init - j
//					- rect_side);
//		}
//	}
//}

void drawPolygon() {
	int start_x = 100, start_y = 100, index;
	glBegin(GL_LINES);
	for (index = 0; index < (main_width-180); index += 20) {
		glVertex2f(start_x + index, start_y);
		glVertex2f(start_x + index, main_height - 100);
		glVertex2f(start_x, start_y + index);
		glVertex2f(main_width - 100, start_y + index);
	}
	glEnd();

}

//#define	checkImageWidth 64
//#define	checkImageHeight 64
//static GLubyte checkImage[checkImageHeight][checkImageWidth][4];
//
//void makeCheckImage(void) {
//	int i, j, c;
//
//	for (i = 0; i < checkImageHeight; i++) {
//		for (j = 0; j < checkImageWidth; j++) {
//			c = ((((i&0x8)==0)^((j&0x8))==0))*255;
//			checkImage[i][j][0] = (GLubyte) c;
//			checkImage[i][j][1] = (GLubyte) c;
//			checkImage[i][j][2] = (GLubyte) c;
//			checkImage[i][j][3] = (GLubyte) 255;
//		}
//	}
//
//	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
//
//	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
//	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
//	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
//	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
//
//	glTexImage2D(GL_TEXTURE_2D, 0, 4, checkImageWidth, checkImageHeight,
//	0, GL_RGBA, GL_UNSIGNED_BYTE, checkImage);
//
//	//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//	glEnable(GL_TEXTURE_2D);
//	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
//	glBegin(GL_QUADS);
//	glTexCoord2f(0.0, 0.0);
//	glVertex3f(-2.0, -1.0, 0.0);
//	glTexCoord2f(0.0, 1.0);
//	glVertex3f(-2.0, 1.0, 0.0);
//	glTexCoord2f(1.0, 1.0);
//	glVertex3f(0.0, 1.0, 0.0);
//	glTexCoord2f(1.0, 0.0);
//	glVertex3f(0.0, -1.0, 0.0);
//	glEnd();
//	glFlush();
//	glDisable(GL_TEXTURE_2D);
//
//}


void scaleObject() {

	if (fixed_point_x > 1 && fixed_point_y > 1) {
		// get the object back to initial place
		glTranslatef(fixed_point_x, fixed_point_y, 0.0);

		glScalef((float)(X- fixed_point_x)/1000 + 1, (float)(Y - fixed_point_y)
				/1000 + 1, 1.0);

		// get the object to origin
		glTranslatef(-fixed_point_x, -fixed_point_y, 0.0);
	}
}

void transformObject() {

	glColor3f(0.4, 0.3, 0.9);
	glBegin(GL_LINES);
	glVertex2f(0.0, 0.0);
	glVertex2f(110.0, 110.0);
	glEnd();

	glMatrixMode(GL_MODELVIEW);
	float mat[16];
	glGetFloatv(GL_MODELVIEW_MATRIX,mat);
	glLoadIdentity();
	if (refresh_display == GL_FALSE) {
		scaleObject();
		glMultMatrixf(mat);
	} else {
		refresh_display = GL_FALSE;
	}

	drawPolygon();
	//makeCheckImage();

	glColor3f(0.1, 0.3, 0.9);
	glBegin(GL_LINES);
	glVertex2f(0.0, 0.0);
	glVertex2f(110.0, 110.0);
	glEnd();

	//	sprintf(stderr, "Fixed Point (%d, %d)", fixed_point_x, fixed_point_y);
	glPushMatrix();
	glLoadIdentity();
	glColor3f(0.8, 0.6, 0.2);
	glPointSize(5.0);

	// if we are in the proper scaling mode, change the point on the screen back to screen cordinates.
	double winX, winY, winZ;
	if (drawingMode == MID_TERM_PROPER_SCALING) {

		/* Get window coordinates */
		gluProject(fixed_point_x, fixed_point_y, 0.0, mvmatrix, projmatrix,
				viewport, &winX, &winY, &winZ);

	} else {
		winX = fixed_point_x;
		winY = fixed_point_y;
	}

	glBegin(GL_POINTS);
	glVertex2f(winX, winY);
	glEnd();
	glPopMatrix();
	glFlush();
}

void catchTheBug() {

	fprintf(stderr, "Catching Bugs\n");

	glLoadIdentity();
	float screen_center_x = (float)main_width/2.0;
	float screen_center_y = (float)main_height/2.0;

	//	if (X == 0 && Y == 0) {
	//		X = screen_center_x;
	//		Y = screen_center_y;
	//	}

	float current_x = rand_x * main_width;
	float current_y = rand_y * main_height;
	bug_found = X < (current_x + 2) && X> (current_x - 2) && Y
			< (current_y + 2) && Y> (current_y - 2);

	if (bug_found == GL_TRUE) {
		rand_x = drand48();
		rand_y = drand48();

		// flash the screen
		glColor3f(0.8, 0.3, 0.77);
		int j = 0;
		for (j = 0; j < 10; j++) {
			glRecti(drand48() * main_width, drand48() * main_height, drand48()
					* main_width, drand48() * main_height);
		}

		bug_found = GL_FALSE;
	}

	// draw the cross hair
	glColor3f(0.8, 0.2, 0.7);
	glLineWidth(1.0);
	glBegin(GL_LINES);
	glVertex2f(screen_center_x - 20, screen_center_y);
	glVertex2f(screen_center_x + 20, screen_center_y);
	glVertex2f(screen_center_x, screen_center_y - 20);
	glVertex2f(screen_center_x, screen_center_y + 20);
	glEnd();

	if (drawLinesInBugCatcher == GL_TRUE) {
		glColor3f(0.1, 0.6, 0.55);
		glBegin(GL_LINES);
		glVertex2f(screen_center_x, screen_center_y);
		glVertex2f(X, Y);
		glEnd();
	}

	glLoadIdentity();
	glTranslatef(screen_center_x, screen_center_y, 0.0);

	switch (transformation_mode) {
	case 0:
		glScalef(1, 1, 0);
		break;
	case 1:
		glScalef(-1, 1, 0);
		break;
	case 2:
		glScalef(1, -1, 0);
		break;
	case 3:
		glScalef(-1, -1, 0);
		break;
	case 4:
		glRotatef(90, 0.0, 0.0, 1.0);
		break;
	case 5:
		glRotatef(90, 0.0, 0.0, 1.0);
		glScalef(-1, 1, 0);
		break;
	case 6:
		glRotatef(90, 0.0, 0.0, 1.0);
		glScalef(1, -1, 0);
		break;
	case 7:
		glRotatef(90, 0.0, 0.0, 1.0);
		glScalef(-1, -1, 0);
		break;
	}

	//	glScalef((float)Y/X, (float)X/Y, 0);
	glTranslatef(-screen_center_x, -screen_center_y, 0.0);

	if (drawLinesInBugCatcher == GL_TRUE) {
		glColor3f(0.1, 0.6, 0.55);
		glBegin(GL_LINES);
		glVertex2f(screen_center_x, screen_center_y);
		glVertex2f(X, Y);
		glEnd();
	}

	glutSolidTetrahedron();

	glPointSize(10.0);
	glBegin(GL_POINTS);
	glColor3f(0.2, 0.2, 0.7);
	glVertex2f(rand_x * main_width, rand_y * main_height);
	glColor3f(0.7, 0.6, 0.99);
	glVertex2f(X, Y);
	glEnd();

	glLoadIdentity();
}

/***************************************************************/
/************************ DRAW STUFF ***************************/
/***************************************************************/

void drawObjs() {
	glColor3f(0.9, 0.8, 0.4);

	switch (drawingMode) {
	case MID_TERM_SCALING:
		transformObject();
		break;
	case MID_TERM_PROPER_SCALING:
		transformObject();
		break;
	case BUG_CATCHER:
		catchTheBug();
		break;
	case TRANSFORM_TEAPOT:
		break;

	}

}

/***************************************************************/
/*********************** MOUSE CALLBACKS ***********************/
/***************************************************************/

/* Mouse Button  UP/DOWN  or DOWN/UP transition only */
void mouse(int button, int state, int x, int y) {
	X = x;
	Y = main_height - y - 1; /* Invert to right-handed system */
	button = (1 << button);

	MouseModifiers = glutGetModifiers();
	/* bitmask with GLUT_ACTIVE_SHIFT, GLUT_ACTIVE_CTRL, GLUT_ACTIVE_ALT */

	if (state == GLUT_DOWN) {
		mouseButtons |= button;
		mouse_just_down = GL_TRUE;

		mouse_down_x = X;
		mouse_down_y = Y;

		if (drawingMode == MID_TERM_PROPER_SCALING) {
			glGetIntegerv(GL_VIEWPORT,viewport);
			glGetDoublev(GL_MODELVIEW_MATRIX,mvmatrix);
			glGetDoublev(GL_PROJECTION_MATRIX,projmatrix);
			/* realY = viewport[3] - (GLint) Y - 1; */
			gluUnProject((GLdouble) X, (GLdouble) Y, 0.0, mvmatrix, projmatrix,
					viewport, &wx, &wy, &wz);
			fixed_point_x = wx;
			fixed_point_y = wy;
		} else {
			fixed_point_x = X;
			fixed_point_y = Y;
		}

	} else {
		//mouse_just_down = GL_TRUE;
		mouseButtons &= (~(button));
	}
	glutPostRedisplay();
}

/* Mouse motion WHILE BUTTON DOWN  */
/*  COMPLAINS  if you call glutGetModifiers()  here!! */
void motion(int x, int y) {
	X = x;
	Y = main_height - y - 1; /* Invert to right-handed system */

	mouse_just_down = GL_FALSE;

	switch (mouseButtons) {
	case 0x1: /* Left Mouse Button   */
		break;
	case 0x2: /* Middle Mouse Button */
		break;
	case 0x4: /* Right Mouse Button  */
		break;
	}

	glutPostRedisplay();

}

/* Mouse motion WHILE ALL BUTTONS UP  */
/*  COMPLAINS  if you call glutGetModifiers()  here!! */
void passive(int x, int y) {
	X = x;
	Y = main_height - y - 1;
	glutPostRedisplay();
}

/* Handle Keyboard */
void keyboard(unsigned char key, int x, int y) {
	/*  Option: To force all lower case */
	/* if (key >= 'A' && key <= 'Z') key = (key-'A') + 'a'; */

	//	glMatrixMode(GL_PROJECTION);
	//	glLoadIdentity();
	//	gluOrtho2D(0.0, (double)main_width-1, 0.0, (double)main_height-1);
	//	glMatrixMode(GL_MODELVIEW);

	switch (key) {
	case 'h':
		fprintf(stderr,"Help: goes here.\n");
		break;
	case '1':
		drawingMode = MID_TERM_SCALING;
		break;
	case '2':
		drawingMode = MID_TERM_PROPER_SCALING;
		break;
	case '3':
		drawingMode = BUG_CATCHER;
		//		glMatrixMode(GL_PROJECTION);
		//		glLoadIdentity();
		//		gluOrtho2D(-1.0, 1.0, -1.0, 1.0);
		//		glMatrixMode(GL_MATRIX_MODE);
		break;
	case '4':
		drawingMode = TRANSFORM_TEAPOT;
		break;
	case ' ':
		transformation_mode = (transformation_mode + 1) % TRANSFORMATION_MODES;
		break;
	case 'a':
		drawLinesInBugCatcher = drawLinesInBugCatcher == GL_FALSE ? GL_TRUE
				: GL_FALSE;
		break;
	case 'r':
		refresh_display = GL_TRUE;
		break;
	case 'q':
	case 27: /* 27 -> ESC */
		fprintf(stderr,"Normal Exit.\n");
		exit(EXIT_SUCCESS);
		break;
	default:
		fprintf(stderr,"Unhandled key: %c [%d] \n",key,key);
	}

	glutPostRedisplay();
}

/***************************************************************/
/************************** INIT GL ****************************/
/***************************************************************/

void initGL() {
	glClearColor(0.0, 0.0, 0.0, 0.0);
}

/***************************************************************/
/************************ GLUT STUFF ***************************/
/***************************************************************/

void reshape(int w, int h) {
	fprintf(stderr,"Comment this out: reshape(%d,%d) \n",w,h);
	w = Max(w,128);
	h = Max(h,128);
	main_height = h;
	main_width = w;
	glutReshapeWindow(w, h);
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluOrtho2D(0.0, (double)w-1, 0.0, (double)h-1);

	glMatrixMode(GL_MODELVIEW);
}

void visibility(int state) {
	/* May also want to check PARTIAL DAMAGE that
	 does not result in a call to the visibility callback:
	 if (glutLayerGet(GLUT_NORMAL_DAMAGED)) {
	 redraw = TRUE; }
	 */
	fprintf(stderr,"Comment this out: visibility: %s\n",
	(state==GLUT_VISIBLE)?"visible":"invisible");
	if (state == GLUT_VISIBLE) {
		glutPostRedisplay();
	}
}

void display(void) {
	fprintf(stderr,"Comment this out: display\n");

	glClear(GL_COLOR_BUFFER_BIT);

	drawObjs();

	glutSwapBuffers();
}

/***************************************************************/
/**************************** MAIN *****************************/
/***************************************************************/

int main(int argc, char **argv) {
	/* GLUT Initialization */
	glutInit(&argc, argv);
	glutInitWindowSize(500, 500);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);

	/* Create a new window and give a title to it */
	main_window = glutCreateWindow(":: PS4 : Eran Chinthaka ::");

	/* Initialize OpenGL stuff */
	initGL();

	/* Post Event Handlers */
	glutReshapeFunc(reshape);
	glutVisibilityFunc(visibility);
	glutDisplayFunc(display);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);
	glutPassiveMotionFunc(NULL);

	glutKeyboardFunc(keyboard);
	fprintf(stderr,"Press ESC or 'q' to Exit.\n");

	glutIdleFunc(NULL);

	glutMainLoop();

	return (EXIT_SUCCESS);
}
